package org.docmanagement.services.impl;

import java.util.List;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.exception.ExceptionUtils;
import org.docmanagement.commons.service.GenericServiceImpl;
import org.docmanagement.data.Element;
import org.docmanagement.data.File;
import org.docmanagement.data.Link;
import org.docmanagement.services.ElementException;
import org.docmanagement.services.ElementService;
import org.hibernate.SessionFactory;
import org.hibernate.criterion.DetachedCriteria;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Restrictions;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service("elementService")
@Transactional
public class ElementServiceImpl extends GenericServiceImpl<Element> implements ElementService {

	@Autowired
    public ElementServiceImpl(SessionFactory sessionFactory) {
        super(sessionFactory);
    }

	protected Class<Element> getObjectClass() {
		return Element.class;
	}

	public List<? extends Element> findByCategoryId(Integer id) throws ElementException {
		try {
			return (List<? extends Element>) findByPropertyId(Element.class, id, "categories");
		} catch (Exception e) {
			logger.error(e.getMessage(), e);
			throw new ElementException("Can't get elements for category id: " + id, e);
		}
	}

	/**
	 * @see org.docmanagement.services.ElementService#findByMainCategoryId(java.lang.Integer)
	 */
	public List<? extends Element> findByMainCategoryId(Integer id) throws ElementException {
		try {
			return findByPropertyId(File.class, id, "mainCategory");
		} catch (Exception e) {
			logger.error(e.getMessage(), e);
			throw new ElementException("Can't get elements for main category id: " + id, e);
		}
	}

	@SuppressWarnings("unchecked")
	public List<Element> findWithNoCategory() throws ElementException {
		try {
			
			DetachedCriteria detachedCriteria = DetachedCriteria.forClass(Element.class); 
			detachedCriteria.add(Restrictions.isEmpty("categories"));
			detachedCriteria.addOrder(Order.asc("id"));
			return getHibernateTemplate().findByCriteria(detachedCriteria);
		} catch (Exception e) {
			logger.error(e.getMessage(), e);
			throw new ElementException("Can't get elements with no category", ExceptionUtils.getRootCause(e));
		}
	}

	public List<? extends Element> findByKeywordId(Integer id) throws ElementException {
		try {
			return findByPropertyId(Element.class, id, "keywords");
		} catch (Exception e) {
			logger.error(e.getMessage(), e);
			throw new ElementException("Can't get elements for keyword id: " + id, e);
		}
	}

	public List<? extends Element> findByKeywordIds(Integer[] ids) throws ElementException {
		try {
			return findByPropertyIds(Element.class, ids, "keywords");
		} catch (Exception e) {
			logger.error(e.getMessage(), e);
			throw new ElementException("Can't get elements for keyword ids: " + ids, e);
		}
	}

	protected List<? extends Element> findByPropertyIds(Class<? extends Element> c, Integer[] ids, String property)
			throws ElementException {
		try {
			DetachedCriteria criteria = DetachedCriteria.forClass(c);
			criteria.createCriteria(property).add(Restrictions.in("id", ids));
			@SuppressWarnings("unchecked")
			List<? extends Element> elements = criteria.getExecutableCriteria(getSession()).list();
			return elements;
		} catch (Exception e) {
			logger.error(e.getMessage(), e);
			throw new ElementException("Can't get elements for keyword ids: " + ids, e);
		}
	}

	protected List<? extends Element> findByPropertyId(Class<? extends Element> c, Integer id, String property)
			throws ElementException {
		try {
			DetachedCriteria criteria = DetachedCriteria.forClass(c);
			criteria.createCriteria(property).add(Restrictions.eq("id", id));
			@SuppressWarnings("unchecked")
			List<? extends Element> elements = criteria.getExecutableCriteria(getSession()).list();
			return elements;
		} catch (Exception e) {
			logger.error(e.getMessage(), e);
			throw new ElementException("Can't get elements for category id: " + id, e);
		}
	}

	public List<? extends Element> findByTitle(String titlePattern) throws ElementException {
		return findByProperty(Element.class, "title", titlePattern);
	}

	public List<? extends Element> findByDescription(String descPattern) throws ElementException {
		return findByProperty(Element.class, "description", descPattern);
	}

	public List<? extends Element> findByTitle(String titlePattern, char type) throws ElementException {
		Class<? extends Element> c = null;
		if (type == File.FILE_TYPE)
			c = File.class;
		else if (type == Link.LINK_TYPE)
			c = Link.class;
		else
			c = Element.class;
		return findByProperty(c, "title", titlePattern);
	}

	public List<? extends Element> findByContent(String contentPattern) throws ElementException {
		try {
			List<? extends Element> files = findByProperty(File.class, "file", contentPattern);
			List<? extends Element> links = findByProperty(Link.class, "url", contentPattern);
			CollectionUtils.addAll(files, links.iterator());
			return files;
		} catch (Exception e) {
			logger.error(e.getMessage(), e);
			throw new ElementException("Can't get elements for content: " + contentPattern, e);
		}
	}

	protected List<? extends Element> findByProperty(Class<? extends Element> c, String property, String pattern)
			throws ElementException {
		try {
			DetachedCriteria criteria = DetachedCriteria.forClass(c);
			criteria.add(Restrictions.like(property, pattern));
			@SuppressWarnings("unchecked")
			List<? extends Element> elements = criteria.getExecutableCriteria(getSession()).list();
			return elements;
		} catch (Exception e) {
			logger.error(e.getMessage(), e);
			throw new ElementException("Can't get elements for property: " + property, e);
		}
	}
}
